#include "Renderer.h"
#include "TriMesh.h"
#include "Surface.h"

Renderer::renderer( int width, int height )
{
	create_renderer();
}

Renderer::~renderer()
{
}

Renderer::create_renderer()
{
	create_renderer_win();
}

Renderer::create_renderer_win()
{
	// select the pixelformat
	PIXELFORMATDESCRIPTOR pfd;

	memset( &pfd, 0, sizeof(pfd));
	
	pfd.nSize = sizeof( pfd );
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
				PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 16; 
	pfd.cDepthBits = 16;

	int pixel_format = ChoosePixelFormat( hDC, &pfd );
	if ( pixel_format == 0 )
	{
		constructed = false;
		return;
	}

	bool success = SetPixelFormat( hDC, pixel_format, &pfd );

	if ( !success )
	{
		constructed = false;
		return;
	}

	// create OpenGL context
	hRC = wglCreateContext( hDC );
	if ( !hRC )
	{
		constructed = false;
		return;
	}

	constructed = true;

	get_driver_information();
	init_state();
}

void Renderer::get_driver_information()
{
	Console->printf( "%s\n", glGetString( GL_VENDOR ) );
	Console->printf( "%s\n", glGetString( GL_RENDERER ) );
	Console->printf( "%s\n", glGetString( GL_VERSION ) );
	Console->printf( "%s\n", gluGetString( GLU_VERSION ) );
	
	Console->printf( "%s\n", glGetString(GL_EXTENSIONS) );
}

void Renderer::set_background_color( const Color &color )
{
	glClearColor( color.r, color.g, color.b, 1.0f );
}

void Renderer::display_back_buffer()
{
	glFlush();
	swap_buffers();
}

void Renderer::clear_back_buffer()
{
	glDisable( GL_DITHER );
	glEnable( GL_SCISSOR_TEST );
	glScissor( 0, 0, width, height );
	glClear( GL_COLOR_BUFFER_BIT );
	glDisable( GL_SCISSOR_TEST );
	glEnable( GL_DITHER );
}

void Renderer::clear_zbuffer()
{
	glDisable( GL_DITHER );
	glEnable( GL_SCISSOR_TEST );
	glScissor( 0, 0, width, height );
	glClear( GL_DEPTH_BUFFER_BIT );
	glDisable( GL_SCISSOR_TEST );
	glEnable( GL_DITHER );
}

void Renderer::clear_buffers()
{
	glDisable( GL_DITHER );
	glEnable( GL_SCISSOR_TEST );
	glScissor( 0, 0, width, height );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glDisable( GL_SCISSOR_TEST );
	glEnable( GL_DITHER );
}

void Renderer::init_state()
{
	// do back face culling
	glEnable( GL_CULL_FACE );
	glCullFace( GL_BACK );
	glFrontFace( GL_CCW );
	
	// use vertex arrays ( much faster )
	glEnableClientState( GL_VERTEX_ARRAY );

	// ambient lighting
	GLfloat black[] = { 0.0f, 0.0f, 0.0f, 1.0f };
	glLightModelfv( GL_LIGHT_MODEL_AMBIENT, black );
}

void Renderer::draw( const Polyline& poly )
{
	// set world matrix
	const Matrix3& rot = poly.world_rotate();
	const Vector3& trans = poly.world_translate();

	Real scale = poly.world_scale();
	GLfloat model_to_world[16] = {
			scale*rot[0][0], scale*rot[1][0], scale*rot[2][0], 0.0f,
			scale*rot[0][1], scale*rot[1][1], scale*rot[2][1], 0.0f,
			scake*rot[0][2], scale*rot[1][2], scale*rot[2][2], 0.0f,
			trans.x        , trans.y        , trans.z        , 1.0f };

	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glMultMatrixf( model_to_world );

	// render the object
	glBegin( GL_LINES );

	// draw line
	const Vector3* vertex = poly.vertices();
	unsigned int l;
	if ( poly.colors() )
	{
		const Colors* colors = poly.colors();
		for ( l = 0; l < poly.get_num_lines() - 1; l++ )
		{
			glColor3fv( (GLfloat*) color++ );
			glvertex3fv( (GLfloat*) vertex++ );
			glColor3fv( (GLfloat*) color++ );
			glvertex3fv( (GLfloat*) vertex++ );
		}

		// close polyline
		if ( poly.closed() )
		{
			glColor3fv( (GLfloat*) color );
			glvertex3fv( (GLfloat*) vertex );
			glColor3fv( (GLfloat*) poly.colors() );
			glvertex3fv( (GLfloat*) poly.vertices() );
		}
	}
	else
	{
		for ( l = 0; l < poly.get_num_lines() - 1; l++ )
		{
			glVertex3fv( (GLfloat*) vertex++ );
			glVertex3fv( (GLfloat*) vertex++ );
		}
		
		if ( poly.closed() )
		{
			glVertex3fv( (GLfloat*) vertex );
			glVertex3fv( (GLfloat*) poly.vertices() );
		}
	}

	glEnd();

	glMatrixMode( GL_MODELVIEW );
	glPopMatrix();
}

void Renderer::draw( const TriMesh& mesh )
{
	//set world matrix
	const Matrix3& rot = mesh.world_rotate();
	const Vector3& tran = mesh.world_translate();
	Real scale = mesh.world_scale();

	GLfloat model_to_world[16] = {
			scale*rot[0][0], scale*rot[1][0], scale*rot[2][0], 0.0f,
			scale*rot[0][1], scale*rot[1][1], scale*rot[2][1], 0.0f,
			scake*rot[0][2], scale*rot[1][2], scale*rot[2][2], 0.0f,
			tran.x         , tran.y         , tran.z         , 1.0f };


	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glMultMatrixf( model_to_world );

	//render the object

	GLfloat* vertices = (GLfloat*) mesh.vertices();
	glVertexPointer( 3, GL_FLOAT, 0, vertices );

	GLfloat* normals = (GLfloat*) mesh.normals();

	if ( normals )
	{
		glEnableClientState( GL_NORMAL_ARRAY );
		glNormalPointer( GL_FLOAT, 0, normals );
	}
	else
	{
		glDisableClientState( GL_NORMAL_ARRAY );
	}

	GLfloat* colors = (GLfloat*) mesh.colors();
	if ( colors )
	{
		glEnableClientState( GL_COLOR_ARRAY );
		glColorPointer( 3, GL_FLOAT, colors );
	}
	else
	{
		glDisableClientState( GL_COLOR_ARRAY );
	}

	GLfloat* textures = (GLfloat*) mesh.textures();
	if ( textures )
	{
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, textures );
	}
	else
	{
		glDisableClientState( GL_TEXTURE_COORD_ARRAY );
	}

	glDrawElements( GL_TRIANGLES, 3*mesh.get_num_triangles(),
		GL_UNSIGNED_INT, mesh.connectivity() );

	glMatrixMode( GL_MODELVIEW );
	glPopMatrix();
}


Renderer::set_alpha_state( AlphaState* alpha )
{
	if ( alpha->blend_enabled() )
	{
		glEnable( GL_BLEND );
		glBlendFunc( alpha->src_blend(), alpha->dst_blend() );
	}
	else
	{
		glDisable( GL_BLEND );
	}

	if ( alpha->test_enabled() )
	{
		glEnable( GL_ALPHA_TEST );
		glAlphaFunc( alpha->test(), alpha->reference() );
	}
	else
	{
		glDisable( GL_ALPHA_TEST );
	}
}

Renderer::set_dither_state( DitherState* dither )
{
	if ( dither->enabled() )
		glEnable( GL_DITHER );
	else
		glDisable( GL_DITHER );
}

Renderer::set_fog_state( FogState* fog )
{
	if ( fog->enabled() )
	{
		glEnable( GL_FOG );
		glFogf( GL_FOG_START, fog->start() );
		glFogf( GL_FOG_END, fog->end() );

		GLfloat color[4] = {
			fog->color().r,
			fog->color().g,
			fog->color().b,
			1.0
		};
		
		glFogfv( GL_FOG_COLOR, color );

		glFogf( GL_FOG_DENSITY, fog->density() );
		glFogi( GL_FOG_MODE, fog->dfunction() );
		glHint( GL_FOG_HINT, fog->afunction() );
	}
	else
	{
		glDisable( GL_FOG );
	}
}
		
void Renderer::set_light_state( LightState* light )
{
	unsigned int num = light->num_lights();

	if ( num > 0 )
	{
		glEnable( GL_LIGHTING );

		GLfloat ambient[4] = { 0.0, 0.0, 0.0, 1.0 };
		GLfloat color[4];
		color[3] = 1.0;

		for( int i = 0; i < num; i++ )
		{
			Light* li = light->get(i);
			GLint index = GL_LIGHT0 + i;

			glEnable( index );
			color[0] = light->ambient().r;
			color[1] = light->ambient().g;
			color[2] = light->ambient().b;
			glLightfv( index, GL_AMBIENT, color );

			color[0] = light->diffuse().r;
			color[1] = light->diffuse().g;
			color[2] = light->diffuse().b;
			glLightfv( index, GL_DIFFUSE, color );

			color[0] = light->specular().r;
			color[1] = light->specular().g;
			color[2] = light->specular().b;
			glLightfv( index, GL_SPECULAR, color );

			if ( light->attenuate() )
			{
				glLightf( index, GL_CONSTANT_ATTENUATION,
					light->constant() );
				glLightf( index, GL_LINEAR_ATTENUATION,
					light->linear() );
				glLightf( index, GL_QUADRATIC_ATTENUATION,
					light->quadratic() );
			}
			else
			{
				glLightf( index, GL_CONSTANT_ATTENUATION, 1.0 );
				glLightf( index, GL_LINEAR_ATTENUATION, 1.0 );
				glLightf( index, GL_QUADRATIC_ATTENUATION, 1.0 );
			}

			if ( light->get_type() == GL_AMBIENT )
			{
				ambient[0] += light->ambient().r;
                ambient[1] += light->ambient().g;
                ambient[2] += light->ambient().b;
			}
	
			GLfloat pos_param[4];
			switch( light->get_type() )
			{
			case GL_DIRECTIONAL:
				DirectionalLight* dl = (DirectionalLight*) light;
				pos_param[0] = -dl->direction().x;
				pos_param[1] = -dl->direction().y;
				pos_param[2] = -dl->direction().z;
				pos_param[3] = 1.0;
				glLightfv( index, GL_POSITION, pos_param );
				break;
			case GL_POINT:
				PointLight* pl = (PointLight*) light;
				pos_param[0] = pl->location().x;
                pos_param[1] = pl->location().y;
                pos_param[2] = pl->location().z;
                pos_param[3] = 1.0;
				break;
			}

			
			if ( light->get_type() == GL_SPOT )
            {
                SpotLight* sl = (SpotLight*) light;
                glLightf( index,GL_SPOT_CUTOFF,
                    180.0*sl->get_angle()/PI );
                glLightfv( index,GL_SPOT_DIRECTION,
                    (const GLfloat*)sl->direction() );
                glLightf( index,GL_SPOT_EXPONENT,
                    sl->exponent() );
            }
            else
            {
                GLfloat default_direction[3] = { 0.0, 0.0, -1.0 };
                glLightf( index, GL_SPOT_CUTOFF, 180.0 );
                glLightfv( index, GL_SPOT_DIRECTION, default_direction );
                glLightf( index, GL_SPOT_EXPONENT, 0.0 );
            }
		}
		
		glLightModelfv( GL_LIGHT_MODEL_AMBIENT, ambient );
		for ( int i = num; i < LightState::MAX_LIGHTS; i++ )
			glDisable( GL_LIGHT0 + i );
	}
	else
	{
		glDisable( GL_LIGHTING );
	}
}

void Renderer::set_material_state( MaterialState* mater )
{
	GLfloat color[4];
	color[3] = 1.0;

	color[0] = mater->emissive().r;
	color[1] = mater->emissive().g;
	color[2] = mater->emissive().b;
	glMaterialv( GL_FRONT, GL_EMISSION, color );

	color[0] = mater->ambient().r;
	color[1] = mater->ambient().g;
	color[2] = mater->ambient().b;
	glMaterialv( GL_FRONT, GL_AMBIENT, color );

	color[0] = mater->diffuse().r;
	color[1] = mater->diffuse().b;
	color[2] = mater->diffuse().g;
	glMaterialv( GL_FRONT, GL_DIFFUSE, color );

	color[0] = mater->specular().r;
	color[1] = mater->specular().b;
	color[2] = mater->specular().g;
	glMaterialv( GL_FRONT, GL_SPECULAR, color );

	glMaterialf( GL_FRONT, GL_SHININESS, mater->shininess() );
}

void Renderer::set_texture_state( TextureState* tex )
{
	unsigned int num_tex = tex->get_num_tex();

	if ( num > 0 )
	{
		glEnable( GL_TEXTURE_2D );

		for ( int i = 0; i < TextureState::MAX_TEXTURES; i++ )
		{
			Texture* texture = tex->get( i );
			if ( !texture )
				continue;

			// generate the texture first time
			if ( tex->user_data() = NULL )
				glGenTextures( 1, &tex->user_data() );

			// bind the texture 
			glBindTexture( GL_TEXTURE_2D, tex->user_data() );
			
			// set up correction mode
			glHint( GL_PERSPECTIVE_CORRECTION_HINT,
				tex->correction() );

			// set up apply mode
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
				tex->apply() );

			glTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
				(const GLfloat*) &tex->blend_color() );

			// set up wrap mode
			switch( tex->wrap() )
			{
			case Texture::WM_CLAMP_S_CLAMP_T:
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
				break;
			case Texture::WM_CLAMP_S_WARP_T:
				glTexParameteri( GL_TEXTURE_2D; GL_TEXTURE_WARP_S, GL_CLAMP );
				glTexParameteri( GL_TEXTURE_2D; GL_TEXTURE_WARP_T, GL_REPEAT );
				break;
			case Texture::WM_WRAP_S_CLAMP_T:
				glTexParameteri( GL_TEXTURE_2D; GL_TEXTURE_WARP_S, GL_REPEAT );
				glTexParameteri( GL_TEXTURE_2D; GL_TEXTURE_WARP_T, GL_CLAMP );
				break;
			case Texture::WM_WRAP_S_WRAP_T:
				glTexParameteri( GL_TEXTURE_2D; GL_TEXTURE_WARP_S, GL_REPEAT );
				glTexParameteri( GL_TEXTURE_2D; GL_TEXTURE_WARP_T, GL_REPEAT );
				break;
			}

			// set up filter mode
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
				tex->filter() );

			// set up mipmap mode
			glTexParametri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
				tex->mipmap() );

			// pass image data to OpenGL
			Image* image = tex->get_image();

			if ( tex->mipmap() == Texture::NONE )
			{
				glTexImage2D( GL_TEXTURE_2D,
					image->get_type(),
					image->get_width(), 
					image->get_height,
					0,
					image->get_format(),
					GL_UNSIGNED_BYTE, image->get_data() );
			}
			else
			{
				gluBuild2DMipmaps( GL_TEXTURE_2D,
					image->get_type(),
					image->get_width(), image->get_height(),
					image->get_format(), GL_UNSIGNED_BYTE,
					image->get_data() );
			}
		}
	}
	else
	{
		glDisable( GL_TEXTURE_2D );
	}
}

void Renderer::set_vertex_color_state( VertexColorState* vertcol )
{
}

void Renderer::set_wireframe_state( WireframeState* wire )
{
	static bool first_time = true;

	if ( first_time )
	{
		glDisable( GL_LINE_STIPPLE );
		first_time = false;
	}

	static bool active = false;

	if ( active )
	{
		if ( !wire->enabled() )
		{
			glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
			active = true;
		}
	}
	else
	{
		if ( wire->enabled() )
		{
			glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
			active = true;
		}
	}
}

void Renderer::set_zbuffer_state( ZBufferState* zbuf )
{
	if ( zbuf->enabled() )
	{
		glEnable( GL_DEPTH_TEST );
		glDepthFunc( zbuf->compare() );
	}
	else
	{
		glDisable( GL_DEPTH_TEST );
		glDepthFunc( GL_ALWAYS );
	}

	if ( zbuf->writeable() )
		glDepthMask( GL_TRUE );
	else
		glDepthMask( GL_FALSE );
}
	

